/**
 * Copyright (c) 2006-2020, JGraph Ltd
 * Copyright (c) 2006-2020, draw.io AG
 *
 * 为指定的UI构建Actions对象，用于管理绘图编辑器的所有操作和命令。
 * 
 * @param {Object} editorUi - 编辑器UI实例，提供图形编辑的用户界面功能
 */
function Actions(editorUi) {
  // 编辑器UI实例引用，用于访问图形编辑器的核心功能
  this.editorUi = editorUi;
  // 存储所有注册的操作命令的映射表
  this.actions = new Object();
  // 初始化默认操作
  this.init();
  // 当前选中的标签页索引：0绘图组件 1数据定义 2对象结构
  this.currentTab = 0;
};

/**
 * 初始化并添加所有默认操作命令。
 * 注册图形编辑器的各种操作功能，包括绘图组件、数据定义、仿真控制等。
 */
Actions.prototype.init = function () {
  const drawioBus = window.parent.$store.state.bus.drawioBus
  const indirectBus = window.parent.$store.state.bus.indirectBus

  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;
  var isGraphEnabled = function () {
    return Action.prototype.isEnabled.apply(this, arguments) && graph.isEnabled();
  };

  (function () {
    /**
     * 切换到绘图组件视图。
     * 显示侧边栏结构面板，隐藏数据扫描面板，切换到绘图模式。
     */
    this.addActionButton('drawing-components', mxUtils.bind(this, function () {
      // myEditor.updateWidth()
      window.sidebarScan && window.sidebarScan.hide()
      window.sidebarStructure && window.sidebarStructure.show()
      document.querySelector('#sidebarContainer').style.display = 'block'
      ui.toggleDrawingMenu();
    }), true, null, "绘图组件");

    /**
     * 切换到数据定义视图。
     * 隐藏侧边栏容器和结构面板，显示数据扫描面板，切换到数据定义模式。
     */
    this.addActionButton('data-definition', mxUtils.bind(this, function () {
      // myEditor.updateWidth(false)
      document.querySelector('#sidebarContainer').style.display = 'none'
      window.sidebarStructure && window.sidebarStructure.hide()
      window.sidebarScan && window.sidebarScan.show()
      ui.toggleDrawingMenu();
    }), true, null, "数据定义");
  }).call(this)

  /**
   * 领域数据操作。
   * 处理与领域数据相关的功能（当前为占位实现）。
   */
  this.addActionButton('data-domain', function () {
    console.log("领域数据");
  }, true, null, "领域数据");

  /**
   * 图片图标操作。
   * 处理图片图标相关的功能（当前为占位实现）。
   */
  this.addActionButton('picture-icon', function () {
    console.log("图片图标");
  }, true, null, "图片图标");

  /**
   * 切换引用状态。
   * 编辑当前选中单元格的引用节点属性。
   */
  this.addAction('toggleQuote', function () {
    editQuoteNode(graph.getSelectionCell(), graph);
  });

  /**
   * 添加附件。
   * 触发添加文件附件的事件，允许用户上传和关联文件。
   */
  this.addAction('addFileBase', function () {
    drawioBus.$emit('addFileBaseClick')
  });

  /**
   * 添加信息元素。
   * 为当前选中的单元格添加信息元素。
   */
  this.addAction('addInfoElement', function () {
    addInfoElement(graph.getSelectionCell(), graph);
  });

  /**
   * 复制父类。
   * 基于当前选中的单元格创建类对象节点。
   */
  this.addAction('copyClass', function () {
    createClassObjNode(graph.getSelectionCell(), graph);
  });

  /**
   * 树形布局。
   * 对选中的节点或整个画布应用紧凑的树形布局算法。
   * 
   * @param {boolean} horizontal - 是否使用水平布局（默认为true）
   * @param {mxCell} tmpCell - 指定的起始单元格（可选）
   * @param {mxCompactTreeLayout} layout - 自定义布局对象（可选）
   */
  this.addAction('treeLayout', function (horizontal, tmpCell, layout) {
    horizontal = horizontal == null ? true : horizontal;

    var tmp = tmpCell || graph.getSelectionCell();
    var roots = null;
    var moveTree = false

    if (tmp == null || graph.getModel().getChildCount(tmp) == 0) {
      if (graph.getModel().getEdgeCount(tmp) == 0) {
        roots = graph.findTreeRoots(graph.getDefaultParent());
      }
    } else {
      roots = graph.findTreeRoots(tmp);
    }

    if (roots != null && roots.length > 0) {
      tmp = roots[0];
      moveTree = roots.length < 2;
    }

    // 存在选中
    if (roots == null && tmp) {
      roots = [tmp];
    }

    var defaultRoots = graph.findTreeRoots(graph.getDefaultParent())
    if (defaultRoots && defaultRoots.length > 1) {
      moveTree = false;
    }

    if (tmp != null) {
      if (!layout) {
        layout = new mxCompactTreeLayout(graph, horizontal);
        layout.edgeRouting = false;
        layout.levelDistance = 30;
        layout.nodeDistance = 10;
        layout.moveTree = moveTree;
      }

      window.myEditor.executeLayout(function () {
        roots.forEach(function (r) {
          layout.execute(graph.getDefaultParent(), r);
        })
      }, true);
    }
  })

  /**
   * 展示引用数据。
   * 显示当前选中单元格的引用数据信息。
   */
  this.addAction('showQuoteData', function () {
    editQuoteNode(graph.getSelectionCell(), graph);
  });

  /**
   * 添加子节点。
   * 在选中节点下添加一个新的子节点，并应用树形布局。
   */
  this.addAction('addChildNode', async function () {
    const horizontal = ui.getToolbarValue('treeLayoutHorizon');
    let cell = graph.getSelectionCell();
    window.GRAPH = graph;
    let item = graph.cloneCells([cell]);
    if (cell.value.nodeName === 'quote') {
      // 引用元素的话，变成新建entity
      const row = myEditor.sidebar.cellAndStyleMap.find(i => i.metaId === cell.value.getAttribute('metaId'))
      item = graph.cloneCells([row.cell])
    }
    window.CELL = item[0];

    const newCell = item[0];
    // 避免clone出的cell重叠
    newCell.geometry.y += 50;
    newCell.getValue().setAttribute('objectId', '');
    console.log('添加子节点', newCell);
    const newName = await getObjectLabelByCell(newCell, null);
    await new utils().putLabel(newCell, newName, resolveCell(newCell))

    var layout = new mxCompactTreeLayout(graph, !!horizontal);
    layout.edgeRouting = false;
    layout.levelDistance = 30;
    layout.nodeDistance = 10;

    graph.addCells(item);

    graph.addChildNode(cell, item, layout);

    var action = window.myEditor.actions.get('treeLayout');

    action.funct(horizontal, cell, layout);
  });

  /**
   * 添加SysML图表。
   * 添加系统建模语言图表（当前为占位实现）。
   */
  this.addAction('addSysmlDiagram', async function (currentObj) {
    console.log('添加图表');
  });

  /**
   * 添加SysML绘图图表。
   * 打开添加结构图表的对话框，用于创建绘图类型的SysML图表。
   */
  this.addAction('addSysmlDrawingDiagram', async function (currentObj) {
    let cell = graph.getSelectionCell();
    drawioBus.$emit('openAddStructureDialog', {
      name: currentObj.name,
      metaId: currentObj.id,
      type: currentObj.type,
      parentId: cell.value.nodeName === 'entity' ? cell.value.getAttribute('objectId') : cell.value.getAttribute('quoteId'),
      typeList: currentObj.typeList,
      createSource: 'graph',
      params: {
        id: currentObj.relateId,
        metaId: currentObj.metaId,
      },
      callback: () => {}
    })
  });
  /**
   * 添加SysML数据图表。
   * 打开添加结构图表的对话框，用于创建数据类型的SysML图表。
   */
  this.addAction('addSysmlDataDiagram', async function (currentObj) {
    let cell = graph.getSelectionCell();
    drawioBus.$emit('openAddStructureDialog', {
      name: currentObj.name,
      metaId: currentObj.id,
      type: currentObj.type,
      parentId: cell.value.nodeName === 'entity' ? cell.value.getAttribute('objectId') : cell.value.getAttribute('quoteId'),
      typeList: currentObj.typeList,
      createSource: 'graph',
      params: {
        id: currentObj.relateId,
        metaId: currentObj.metaId,
      },
      callback: () => {
        drawioBus.$emit('addChildGraphOverlay')
      }
    })
  });

  /**
   * 创建状态图。
   * 为选中的组件创建状态迁移图，用于显示组件的状态变化。
   */
  this.addAction('createStatus', function () {
    let cell = graph.getSelectionCell();
    cell.vertex = true;
    let url = contextPath + '/designItem/addStateDiagram';
    const params = {
      partMetaId: cell.value.getAttribute('metaId'),
      partId: cell.value.getAttribute('objectId'),
    };
    window.parent.$MessageBox.confirm('确定创建状态图？', '提示', {
      confirmButtonText: '确认',
      cancelButtonText: '取消',
      type: 'warning'
    }).then(() => {
      window.$axios.post(url,
        params
      ).then((res) => {
        if (res.statusCode === 200) {
          window.parent.$Message({
            type: 'success',
            message: '创建成功!'
          });
          indirectBus.$emit('updateTree')
        }
      })
    })
  });

  /**
   * 选择资源型号。
   * 根据输入推荐适合的资源型号，帮助用户选择合适的资源。
   */
  this.addAction('selectResourceVersion', function () {
    recommendResourceByInputs(graph.getSelectionCell(), graph);
  }).isEnabled = function () {
    return true;
  };

  /**
   * 切换表现形式。
   * 改变选中元素的显示形式，支持多种视觉表现样式。
   * 
   * @param {string} figureType - 要设置的表现形式类型
   */
  this.addAction('toggleFigureType', function (figureType) {
    var cells = graph.getSelectionCells();
    for (var i = 0; i < cells.length; i++) {
      if (cells[i].isEdge()) {
        continue;
      }
      var style = cells[i].getStyle();
      style = figureType + style.substring(style.indexOf(";html"));
      graph.getModel().setStyle(cells[i], style);
    }
  });

  /**
   * 时序图转状态图。
   * 将时间序列图转换为状态迁移图，辅助用户生成状态模型。
   */
  this.addAction('timeSequenceToState', function () {
    ui.timeSequenceToState('');
    //saveAfterHandle();
    // console.log("保存");
  }, null, null, "辅助生成").isEnabled = isGraphEnabled;

  /*
  // 编译/生成/运行
  this.addAction('timeSequenceToState', function(){
        ui.timeSequenceToState('');
  }, true, null, "辅助生成");
  */

  /**
   * 仿真选择。
   * 激活仿真选择模式，允许用户选择要仿真的元素。
   */
  this.addAction('fangzhen', function () {
    drawioBus.$emit('simulationSelect')
  }, true, null, "仿真");

  /**
   * UML排序。
   * 对UML图中的元素进行自动排序，优化图表布局。
   */
  this.addAction('umlsort', function () {
    drawioBus.$emit('umlsort')
  }, true, null, "排序");
  /**
   * 仿真编译运行。
   * 编译、生成和运行仿真代码，为仿真执行做准备。
   */
  this.addAction('simulatCompileRun', function () {
    drawioBus.$emit('simulatCompileRun')
  }, true, null, "编译/生成/运行");

  /**
   * 开始仿真。
   * 启动仿真执行过程，运行已编译的仿真模型。
   */
  this.addAction('simulatStart', async function () {
    drawioBus.$emit('simulatStart')
  }, true, null, "开始");

  /**
   * 暂停仿真。
   * 暂停正在运行的仿真进程，保持当前状态。
   */
  this.addAction('simulatSuspend', function () {
    drawioBus.$emit('simulatSuspend')
  }, true, null, "暂停");

  /**
   * 恢复仿真。
   * 恢复被暂停的仿真进程，继续执行。
   */
  this.addAction('simulatRecover', function () {
    drawioBus.$emit('simulatRecover')
  }, true, null, "恢复");

  /**
   * 单步仿真。
   * 以单步模式执行仿真，逐步调试和分析（当前为占位实现）。
   */
  this.addAction('simulatSingle', function () {
    alert('simulatRecover');
  }, true, null, "单步");

  /**
   * 结束仿真。
   * 停止并结束当前的仿真运行，清理资源。
   */
  this.addAction('simulatOver', function () {
    drawioBus.$emit('simulatOver')
  }, true, null, "结束");

  /**
   * 设置仿真断点。
   * 在仿真执行过程中设置断点，用于调试（当前为占位实现）。
   */
  this.addAction('simulatBreakpoint', function () {
    alert('simulatBreakpoint');
  }, true, null, "断点");

  /**
   * 全屏切换。
   * 在全屏和窗口模式之间切换绘图区域，提供更好的编辑体验。
   */
  this.addAction('fullscreen', function () {
    const classList = window.parent.document.querySelector('.drawioContainer').classList.value
    if (classList.includes(' screen')) {
      window.parent.document.querySelector('.drawioContainer').classList.remove('screen')
    } else {
      window.parent.document.querySelector('.drawioContainer').classList.add('screen')
    }
    myEditor.graphEvent.diagramSizeWather.refreshBound() // 更新图框大小

    setTimeout(function () {
      ui.resetScrollbars();
    }, 100)
    // setTimeout(()=>{
    //     myEditor.graphEvent.graphChange(myEditor.editor.graph.model, {
    //         properties: {changes: []}
    //     })
    // },50)
  }, true, null, "全屏");

  /**
   * 添加属性。
   * 为当前选中的实体元素添加一个新的属性字段。
   */
  this.addAction('addAttr', async function () {
    let cell = graph.getSelectionCell();
    cell.vertex = true;
    let height = cell.geometry.height;
    let text;

    if (!cell.children) return;
    let cloneCell = window.LABELERATTRIBUTE;
    let str = await getObjectNameInfo(cloneCell, cell)
    cloneCell.setAttribute('label', str);
    cloneCell.setAttribute('name', str);
    cloneCell.setAttribute('tooltip', str);

    const style = 'html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=12;spacingLeft=24;spacing=2;spacingBottom=-5;image=img/clipart/deputy.png;';
    // cell长度减去一个edge元素
    const cellLength = cell.children.length - 1;
    if (cellLength === 0) {
      text = new mxCell(cloneCell, new mxGeometry(0, 20, cell.geometry.width, 20), style);
    } else {
      // 获取增加属性的高度最大值 再加一个20
      const max = cell.children.reduce((prev, current) => (current.geometry.y > prev ? current.geometry.y : prev), Number.NEGATIVE_INFINITY);
      text = new mxCell(cloneCell, new mxGeometry(0, max + 20, cell.geometry.width, 20), style);
      cell.geometry.height = height + 20;
    }
    text.vertex = true;
    text.isMajor = 2;
    text.connectable = false;
    text.OrId = cell.id + Date.now();
    text.setLabelErAttributeInfoHaveChange = 'false';
    cell.insert(text.clone());

    graph.setSelectionCell(cell);
    graph.refresh();
    ui.editor.setModified(true)
  });

  let setCell = function (cellClone, tarInitHeight, item, view) {
    cellClone.geometry.y = tarInitHeight;
    item.target.insert(cellClone)
    graph.erSort(item.target)
    view.invalidate(item.target, true, true);
    const state = view.getState(item.target);
    if (state != null) {
      state.invalidStyle = true;
    }
    graph.multistepInherit(item.target, 1)
  }

  /**
   * 设为主键。
   * 将当前选中的属性设置为主键，并更新相关元素的关联关系。
   */
  this.addAction('becomeMajorKey', function () {
    //设为主键
    const view = editor.graph.view;
    let cell = graph.getSelectionCell();
    if (!cell.getValue().getAttribute('objectId')) {
      window.parent.$Message({
        message: '请先保存新添加的逻辑属性再设置为主键！',
        type: 'warning'
      });
      return
    }
    cell.isMajor = 1;
    cell.vertex = true;
    let parentCell = cell.getParent();
    cell.getValue().setAttribute('label', graph.getMajorKeyLabel(cell));
    graph.erSort(parentCell)

    if (parentCell.edges && parentCell.edges.length) {
      parentCell.edges.forEach(item => {
        if (item.value && item.source && item.target) {

          // 逻辑一对多确定关系
          // 逻辑完全分类关系
          // 逻辑不完全分类关系
          // 逻辑多对多关系(无其他数据流转)
          if (['LINE_ONETOMULLINE', 'LINE_COMPLETECLASSIFICATION', 'LINE_NONCOMPLETECLASSIFICATION', 'LINE_MULTOMULLINE'].indexOf(GraphEventUtils.getDisplay(item)) > -1) {
            console.log('逻辑一对多确定关系')
            if (item.source.mxObjectId === parentCell.mxObjectId) {
              let tarMajorNum = 0;
              item.target.children.forEach(item => {
                if (item.isMajor === 1) {
                  tarMajorNum += 1;
                }
              });
              let tarInitHeight = tarMajorNum * 20;
              let cellClone = cell.clone();
              if (!cellClone.isInherit) {
                // 继承设置label为(FK)
                let cellName = cellClone.getValue().getAttribute('name') + ' (FK)';
                cellClone.getValue().setAttribute('label', cellName);
                cellClone.isInherit = true;
                setLogicalPropertyId(cellClone);
              }
              setCell(cellClone, tarInitHeight, item, view);
            }
          }

          // 逻辑一对多非确定关系
          if (item.value.getAttribute('display') === 'LINE_ONETOMULNONLINE') {
            console.log('逻辑一对多非确定关系')
            if (item.source.mxObjectId === parentCell.mxObjectId) {
              let tarNoMajorNum = 0;
              item.target.children.forEach(item => {
                if (item.isMajor === 2) {
                  tarNoMajorNum += 1;
                }
              });
              let pHeight = item.target.geometry.height;
              let tarInitHeight = pHeight - (tarNoMajorNum + 1) * 20;
              let cellClone = cell.clone();
              if (!cellClone.isInherit) {
                // 继承设置label为(FK)
                let cellName = cellClone.getValue().getAttribute('name') + ' (FK)';
                cellClone.getValue().setAttribute('label', cellName);
                cellClone.isInherit = true;
                setLogicalPropertyId(cellClone);
              }
              cellClone.isMajor = 2;
              setCell(cellClone, tarInitHeight, item, view);
            }
          }
        }
      })

    }
    graph.setSelectionCell(cell);
    graph.refresh();

    ui.editor.setModified(true)
  });

  let cancelCell = function (item, parentCell, view, cell) {
    if (item.source.mxObjectId === parentCell.mxObjectId) {
      graph.multistepInherit(item.target, 2, 1, cell)
      item.target.children.forEach(item => {
        if (item.OrId && cell.OrId && item.OrId === cell.OrId) {
          graph.removeCells([item])
        }
      });
      graph.erSort(item.target)
      view.invalidate(item.target, true, true);
      const state = view.getState(item.target);
      if (state != null) {
        state.invalidStyle = true;
      }
    }
  }

  /**
   * 取消主键。
   * 取消当前选中属性的主键状态，并更新相关元素的关联关系。
   */
  this.addAction('cancelMajorKey', function () {
    //取消主键
    const view = editor.graph.view;
    let cell = graph.getSelectionCell();
    cell.getValue().setAttribute('label', cell.getValue().getAttribute('name'));
    cell.vertex = true;
    cell.isMajor = 2;
    let parentCell = cell.getParent();
    graph.erSort(parentCell)

    if (parentCell.edges && parentCell.edges.length) {
      parentCell.edges.forEach(item => {
        // 逻辑一对多确定关系
        if (item.value.getAttribute('display') === 'LINE_ONETOMULLINE') {
          console.log('逻辑一对多确定关系')
          cancelCell(item, parentCell, view, cell)
        }
        // 逻辑一对多非确定关系
        if (item.value.getAttribute('display') === 'LINE_ONETOMULNONLINE') {
          console.log('逻辑一对多非确定关系')
          cancelCell(item, parentCell, view, cell)
        }
        // 逻辑多对多关系(无其他数据流转)
        if (item.value.getAttribute('display') === 'LINE_MULTOMULLINE') {
          console.log('逻辑多对多关系(无其他数据流转)')
          cancelCell(item, parentCell, view, cell)
        }
        // 逻辑完全分类关系
        if (item.value.getAttribute('display') === 'LINE_COMPLETECLASSIFICATION') {
          console.log('逻辑完全分类关系')
          cancelCell(item, parentCell, view, cell)
        }
        // 逻辑不完全分类关系
        if (item.value.getAttribute('display') === 'LINE_NONCOMPLETECLASSIFICATION') {
          console.log('逻辑不完全分类关系')
          cancelCell(item, parentCell, view, cell)
        }
      })
    }

    graph.setSelectionCell(cell);
    graph.refresh();
  });

  // File actions
  /**
   * 新建文件。
   * 在新窗口中打开一个全新的绘图项目。
   */
  this.addAction('new...', function () {
    graph.openLink(ui.getUrl());
  });
  /**
   * 打开文件。
   * 打开本地文件系统中的绘图文件，加载到编辑器中。
   */
  this.addAction('open...', function () {
    window.openNew = true;
    window.openKey = 'open';

    ui.openFile();
  });
  /**
   * 导入文件。
   * 将外部文件中的图形元素导入到当前绘图中，而不替换整个文件。
   */
  this.addAction('import...', function () {
    window.openNew = false;
    window.openKey = 'import';

    // Closes dialog after open
    window.openFile = new OpenFile(mxUtils.bind(this, function () {
      ui.hideDialog();
    }));

    window.openFile.setConsumer(mxUtils.bind(this, function (xml, filename) {
      try {
        var doc = mxUtils.parseXml(xml);
        editor.graph.setSelectionCells(editor.graph.importGraphModel(doc.documentElement));
      } catch (e) {
        mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
      }
    }));

    // Removes openFile if dialog is closed
    ui.showDialog(new OpenDialog(this).container, 320, 220, true, true, function () {
      window.openFile = null;
    });
  }).isEnabled = isGraphEnabled;

  let saving = false
  /**
   * 保存文件。
   * 保存当前绘图的更改，包括表单提交验证和文件保存操作。
   */
  this.addAction('save', function () {
    if (saving) return

    function save() {
      if (saving) return;
      saving = true

      ui.save('').finally(function () {
        saving = false;
      })
    }
    try {
      const frameWindow = myEditor.format.editIframe.contentWindow
      if (frameWindow) {
        frameWindow.submitForm(window, save)
      } else {
        save()
      }
    } catch (e) {
      save()
    }
    saveAfterHandle();
  }, null, null, Editor.ctrlKey + '+S').isEnabled = isGraphEnabled;

  /**
   * 另存为。
   * 将当前绘图以新名称或新格式保存到指定位置。
   */
  this.addAction('saveAs...', function () {
    ui.saveFile(true);
  }, null, null, Editor.ctrlKey + '+Shift+S').isEnabled = isGraphEnabled;
  /**
   * 导出文件。
   * 打开导出对话框，支持多种格式的图像和文件导出。
   */
  this.addAction('export...', function () {
    ui.showDialog(new ExportDialog(ui).container, 300, 304, true, true);
  });
  /**
   * 编辑图表属性。
   * 打开图表编辑对话框，编辑图表的元数据和属性。
   */
  this.addAction('editDiagram...', function () {
    var dlg = new EditDiagramDialog(ui);
    ui.showDialog(dlg.container, 620, 420, true, false);
    dlg.init();
  });
  /**
   * 页面设置。
   * 打开页面设置对话框，配置纸张大小、方向和边距等参数。
   */
  this.addAction('pageSetup...', function () {
    ui.showDialog(new PageSetupDialog(ui).container, 320, 220, true, true);
  }).isEnabled = isGraphEnabled;
  /**
   * 打印。
   * 打开打印对话框，配置打印选项并执行打印操作。
   */
  this.addAction('print...', function () {
    ui.showDialog(new PrintDialog(ui).container, 300, 180, true, true);
  }, null, 'sprite-print', Editor.ctrlKey + '+P');
  /**
   * 预览。
   * 在新窗口中预览当前绘图，方便查看整体效果。
   */
  this.addAction('preview', function () {
    mxUtils.show(graph, null, 10, 10);
  });

  // Edit actions
  /**
   * 撤销操作。
   * 撤销上一次的编辑操作，恢复到之前的状态。
   */
  this.addAction('undo', function () {
    ui.undo();
    // 撤销  恢复连线如果源节点是子节点则刷新源节点和目标节点的state
    window.myEditor.editor.graph.view.revalidate();
  }, null, 'sprite-undo', Editor.ctrlKey + '+Z');
  /**
   * 重做操作。
   * 重新执行被撤销的操作，恢复到重做前的状态。
   */
  this.addAction('redo', function () {
    ui.redo();
  }, null, 'sprite-redo', (!mxClient.IS_WIN) ? Editor.ctrlKey + '+Shift+Z' : Editor.ctrlKey + '+Y');
  /**
   * 剪切。
   * 将选中的元素剪切到剪贴板并从画布中删除。
   */
  this.addAction('cut', function () {
    var cells = null;

    try {
      cells = ui.copyXml();

      if (cells != null) {
        graph.removeCells(cells);
      }
    } catch (e) {
      // ignore
    }

    if (cells == null) {
      mxClipboard.cut(graph);
    }
  }, null, 'sprite-cut', Editor.ctrlKey + '+X');

  (function () {
    // 鼠标位置
    var mousePoint = null;

    /**
     * 复制。
     * 将选中的元素复制到剪贴板，不从画布中删除。
     */
    this.addAction('copy', function () {
      try {
        ui.copyXml();
      } catch (e) {
        // ignore
      }

      try {
        // 将复制的对象id 清空
        mxClipboard.copy(graph);

        const cells = mxClipboard.cells;

        // 不可复制时清空剪贴板中的cells
        if (!graph.isCopyable(cells)) {
          mxClipboard.setCells(null);
          return;
        }

        graph.resetCopyCells(cells)
      } catch (e) {
        ui.handleError(e);
      }

      addClickListener()
    }, null, 'sprite-copy', Editor.ctrlKey + '+C');
    /**
     * 粘贴。
     * 将剪贴板中的内容粘贴到画布上。
     */
    this.addAction('paste', function () {
      paste(mousePoint)

      resetMousePoint();
    }, false, 'sprite-paste', Editor.ctrlKey + '+V');


    /**
     * 复制触发后，记录鼠标点击位置，粘贴时放置到该位置
     */
    var mouseListener = {
      mouseDown: function () {},
      mouseUp: onClick,
      mouseMove: function () {}
    }

    function addClickListener() {
      mousePoint = null;
      removeClickListener()
      graph.addMouseListener(mouseListener)
    }

    function removeClickListener() {
      mousePoint = null;
      graph.removeMouseListener(mouseListener)
    }

    function resetMousePoint() {
      mousePoint = null;
    }

    function onClick(sender, me) {
      var pt = new mxPoint(me.getGraphX(), me.getGraphY());

      var scale = graph.view.scale;
      var tr = graph.view.translate;

      pt.x = (pt.x / scale) - tr.x;
      pt.y = (pt.y / scale) - tr.y;

      mousePoint = pt;
    }
  }).call(this)

  function paste(point) {
    if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent())) {
      let includeEdges = false
      const cells = []
      const cloneCells = graph.cloneCells(mxClipboard.cells);

      cloneCells.forEach(cell => {
        // const cloneCell = graph.cloneCell(cell)
        if (!cell.isEdge()) {
          cell.geometry.x += 20
          cell.geometry.y += 20
        } else {
          includeEdges = true
        }
        cells.push(cell)
      })

      graph.model.beginUpdate()
      try {
        if (cells.length) {
          const addedCells = graph.addCells(cells, undefined, undefined, undefined, undefined, undefined, true)
          if (point) {
            var bb = graph.getBoundingBoxFromGeometry(addedCells, includeEdges);
            graph.cellsMoved(addedCells, point.x - bb.x, point.y - bb.y)
          }
          graph.clearSelection()
          graph.setSelectionCells(addedCells)
        }
      } finally {
        graph.model.endUpdate()
      }
    }
  }

  /**
   * 在此处粘贴。
   * 将剪贴板内容粘贴到鼠标右键菜单触发的位置。
   */
  this.addAction('pasteHere', function (evt) {
    var t = graph.view.translate;
    var s = graph.view.scale;
    var dx = t.x;
    var dy = t.y;

    var x = Math.round(graph.snap(graph.popupMenuHandler.triggerX / s - dx));
    var y = Math.round(graph.snap(graph.popupMenuHandler.triggerY / s - dy));
    paste({
      x: x,
      y: y
    })
  });

  /**
   * 复制尺寸。
   * 复制选中元素的尺寸信息，用于后续将相同尺寸应用到其他元素。
   */
  this.addAction('copySize', function (evt) {
    var cell = graph.getSelectionCell();

    if (graph.isEnabled() && cell != null && graph.getModel().isVertex(cell)) {
      var geo = graph.getCellGeometry(cell);

      if (geo != null) {
        ui.copiedSize = new mxRectangle(geo.x, geo.y, geo.width, geo.height);
      }
    }
  }, null, null, 'Alt+Shift+X');

  /**
   * 粘贴尺寸。
   * 将之前复制的尺寸应用到当前选中的元素。
   */
  this.addAction('pasteSize', function (evt) {
    if (graph.isEnabled() && !graph.isSelectionEmpty() && ui.copiedSize != null) {
      graph.getModel().beginUpdate();

      try {
        var cells = graph.getSelectionCells();

        for (var i = 0; i < cells.length; i++) {
          if (graph.getModel().isVertex(cells[i])) {
            var geo = graph.getCellGeometry(cells[i]);

            if (geo != null) {
              geo = geo.clone();
              geo.width = ui.copiedSize.width;
              geo.height = ui.copiedSize.height;

              graph.getModel().setGeometry(cells[i], geo);
            }
          }
        }
      } finally {
        graph.getModel().endUpdate();
      }
    }
  }, null, null, 'Alt+Shift+V');

  function deleteCells(includeEdges) {
    // Cancels interactive operations
    graph.escape();
    var select = graph.deleteCells(graph.getDeletableCells(graph.getSelectionCells()), includeEdges);
    if (select != null) {
      graph.setSelectionCells(select);
    }
  };

  /**
   * 删除。
   * 删除选中的元素，如果包含时间轴则会清空整个页面。
   */
  this.addAction('delete', function (evt) {
    const cells = graph.getSelectionCells();
    const removeCells = []
    let message = ''
    cells.forEach(cell => {
      if (cell.value && cell.value instanceof Object) {
        if (cell.value.nodeName === 'timeaxis') {
          if (!message.includes('时间轴')) {
            message += '删除时间轴会清空页面所有的元素，'
          }
          window.haveTimeCell = false;
          window.TIMECELL = null;
          const childVertices = graph.getChildVertices(graph.getDefaultParent()).filter(i => i.id !== '3')
          removeCells.push(...childVertices)
        }
      }
      removeCells.push(cell)
    })
    window.parent.$MessageBox.confirm(message + '确认删除？', '系统提示', {
      confirmButtonText: '确认',
      cancelButtonText: '取消',
      type: 'warning'
    }).then(() => {
      graph.removeCells(removeCells);
    })
  }, null, null, 'DeleteFromView');

  /**
   * 从模型中删除单元格。
   * 从数据模型中永久删除指定的单元格，包括数据库和缓存。
   * @param {mxCell[]} cells - 要删除的单元格数组（可选，默认使用选中的单元格）
   */
  this.addAction('deleteCellsFromModel', async function (cells) {
    cells = cells || graph.getSelectionCells();

    const removeCells = []
    const cellIds = []
    const params = [];
    cells.forEach(cell => {
      if (cell.isVertex() && cell.isElement()) {
        const isQuote = cell.value.nodeName === 'quote'
        //为引用对象则获取quoteId，没有则获取objectId
        const keyId = cell.getAttribute(isQuote ? 'quoteId' : 'objectId');
        if (!keyId) {
          removeCells.push(cell)
          return
        }!cellIds.includes(keyId) && cellIds.push(keyId);
        let metaId = cell.getAttribute('metaId');
        params.push({
          id: keyId,
          metaId: metaId
        })
      } else {
        removeCells.push(cell)
      }
    })
    params.length && await window.$axios.post(window.parent.top.g.ApiUrl2 + '/object/batchDeleteObject?', params);
    cellIds.length && graph.getVerticesAndEdges(true, false).forEach((cur) => {
      if (cur.isElement() && cellIds.includes(cur.getAttribute(cur.value.nodeName === 'quote' ? 'quoteId' : 'objectId'))) {
        removeCells.push(cur)
      }
    })
    const select = graph.deleteCells(graph.getDeletableCells(removeCells), true);
    select && graph.setSelectionCells(select);
    // 更新tree
    window.sidebarStructure ? .updateTreeNode();
    // 同步删除数据定义中的节点
    cellIds.length && window.sidebarScan ? .removeNodesByIds(cellIds);
  })

  const MessageBox = window.parent.$MessageBox
  this.addAction(
    'deleteFromModel',
    /**
     * 从模型中删除。
     * 从数据模型中永久删除选中的元素，包括数据库记录。
     */
    mxUtils.bind(this, async function (evt) {
      const confirm = await MessageBox.confirm('此操作将永久删除该数据, 是否继续?', '提示', {
          confirmButtonText: '确认',
          cancelButtonText: '取消',
          type: 'warning',
        })
        .then(() => true)
        .catch(() => false);
      if (!confirm) {
        return;
      }
      const myEditor = window.myEditor;

      await this.get('deleteCellsFromModel').funct();

      if (myEditor.editor.modified) {
        const confirm = await MessageBox.confirm('当前模型未保存，以防数据丢失，是否先进行保存?', '提示', {
            confirmButtonText: '保存',
            cancelButtonText: '取消',
            type: 'warning',
          })
          .then(() => true)
          .catch(() => false);
        if (!confirm) return;
        await myEditor.saveGraph('name');
      }
      window.myEditor.editor.setModified(false);
    }),
    null,
    null
  );
  // }, null, null, 'Ctrl+D');

  /**
   * 删除全部。
   * 删除选中元素及其相关连线。
   */
  this.addAction('deleteAll', function () {
    deleteCells(true);
  });

  /**
   * 删除标签。
   * 清除选中元素的文本标签，保留元素本身。
   */
  this.addAction('deleteLabels', function () {
    if (!graph.isSelectionEmpty()) {
      graph.getModel().beginUpdate();
      try {
        var cells = graph.getSelectionCells();

        for (var i = 0; i < cells.length; i++) {
          graph.cellLabelChanged(cells[i], '');
        }
      } finally {
        graph.getModel().endUpdate();
      }
    }
  }, null, null, Editor.ctrlKey + '+DeleteFromView');
  /**
   * 选择相同类型单元格。
   * 选中当前单元格相同类型的所有元素。
   */
  this.addAction('selectSameTypeCell', function () {
    const value = graph.getSelectionCell().getValue();
    const cells = graph.model.cells

    const sm = Object.values(cells).filter(cell => {
      if (value instanceof Object && cell.value instanceof Object) {
        return value.getAttribute('metaId') === cell.value.getAttribute('metaId')
      } else {
        return typeof value === 'string' && typeof cell.value === 'string'
      }
    })
    graph.setSelectionCells(sm)
  });
  /**
   * 复制元素。
   * 创建选中元素的副本并放置在附近。
   */
  this.addAction('duplicate', function () {
    try {
      graph.setSelectionCells(graph.duplicateCells());
      graph.scrollCellToVisible(graph.getSelectionCell());
    } catch (e) {
      ui.handleError(e);
    }
  }, null, null, Editor.ctrlKey + '+D');
  /**
   * 翻转/旋转元素。
   * 将选中元素翻转或旋转180度。
   */
  this.put('turn', new Action(mxResources.get('turn') + ' / ' + mxResources.get('reverse'), function (evt) {
    graph.turnShapes(graph.getSelectionCells(), (evt != null) ? mxEvent.isShiftDown(evt) : false);
  }, null, null, Editor.ctrlKey + '+R'));
  /**
   * 选择所有节点。
   * 选中所有顶点类型的元素（不包括连线）。
   */
  this.addAction('selectVertices', function () {
    graph.selectVertices(null, true);
  }, null, null, Editor.ctrlKey + '+Shift+I');
  /**
   * 选择所有连线。
   * 选中所有边类型的元素（连接线）。
   */
  this.addAction('selectEdges', function () {
    graph.selectEdges();
  }, null, null, Editor.ctrlKey + '+Shift+E');
  /**
   * 全选。
   * 选中画布上的所有元素。
   */
  this.addAction('selectAll', function () {
    graph.selectAll(null, true);
  }, null, null, Editor.ctrlKey + '+A');
  /**
   * 取消选择。
   * 清除当前选中的所有元素。
   */
  this.addAction('selectNone', function () {
    graph.clearSelection();
  }, null, null, Editor.ctrlKey + '+Shift+A');
  /**
   * 锁定/解锁。
   * 切换选中元素的锁定状态，防止或允许编辑操作。
   */
  this.addAction('lockUnlock', function () {
    if (!graph.isSelectionEmpty()) {
      graph.getModel().beginUpdate();
      try {
        var defaultValue = graph.isCellMovable(graph.getSelectionCell()) ? 1 : 0;
        graph.toggleCellStyles(mxConstants.STYLE_MOVABLE, defaultValue);
        graph.toggleCellStyles(mxConstants.STYLE_RESIZABLE, defaultValue);
        graph.toggleCellStyles(mxConstants.STYLE_ROTATABLE, defaultValue);
        graph.toggleCellStyles(mxConstants.STYLE_DELETABLE, defaultValue);
        graph.toggleCellStyles(mxConstants.STYLE_EDITABLE, defaultValue);
        graph.toggleCellStyles('connectable', defaultValue);
      } finally {
        graph.getModel().endUpdate();
      }
    }
  }, null, null, Editor.ctrlKey + '+L');

  // Navigation actions
  /**
   * 回到首页。
   * 重置视图到初始位置，显示画布的原点。
   */
  this.addAction('home', function () {
    graph.home();
  }, null, null, 'Shift+Home');
  /**
   * 退出组。
   * 退出当前组对象，返回到上一级层次。
   */
  this.addAction('exitGroup', function () {
    graph.exitGroup();
  }, null, null, Editor.ctrlKey + '+Shift+Home');
  /**
   * 进入组。
   * 进入选中的组对象，编辑组内元素。
   */
  this.addAction('enterGroup', function () {
    graph.enterGroup();
  }, null, null, Editor.ctrlKey + '+Shift+End');
  /**
   * 折叠。
   * 折叠选中的元素，隐藏其子元素。
   */
  this.addAction('collapse', function () {
    graph.foldCells(true);
  }, null, null, Editor.ctrlKey + '+Home');
  /**
   * 展开。
   * 展开选中的元素，显示其子元素。
   */
  this.addAction('expand', function () {
    graph.foldCells(false);
  }, null, null, Editor.ctrlKey + '+End');

  // Arrange actions
  /**
   * 置于顶层。
   * 将选中元素移动到所有其他元素的顶部。
   */
  this.addAction('toFront', function () {
    graph.orderCells(false);
  }, null, null, Editor.ctrlKey + '+Shift+F');
  /**
   * 置于底层。
   * 将选中元素移动到所有其他元素的底部。
   */
  this.addAction('toBack', function () {
    graph.orderCells(true);
    // 将图框移至最后
    if (myEditor.editor.graph.backgroundPage) {
      graph.orderCells(true, [myEditor.editor.graph.backgroundPage]);
    }
  }, null, null, Editor.ctrlKey + '+Shift+B');
  /**
   * 组合。
   * 将选中的多个元素组合成一个组对象。
   */
  this.addAction('group', function () {
    if (graph.isEnabled()) {
      var cells = mxUtils.sortCells(graph.getSelectionCells(), true);

      if (cells.length == 1 && !graph.isTable(cells[0]) && !graph.isTableRow(cells[0])) {
        graph.setCellStyles('container', '1');
      } else {
        cells = graph.getCellsForGroup(cells);

        if (cells.length > 1) {
          graph.setSelectionCell(graph.groupCells(null, 0, cells));
        }
      }
    }
  }, null, null, Editor.ctrlKey + '+G');
  /**
   * 取消组合。
   * 将选中的组对象解散成单独的元素。
   */
  this.addAction('ungroup', function () {
    if (graph.isEnabled()) {
      var cells = graph.getSelectionCells();

      graph.model.beginUpdate();
      try {
        var temp = graph.ungroupCells();

        // Clears container flag for remaining cells
        if (cells != null) {
          for (var i = 0; i < cells.length; i++) {
            if (graph.model.contains(cells[i])) {
              if (graph.model.getChildCount(cells[i]) == 0 &&
                graph.model.isVertex(cells[i])) {
                graph.setCellStyles('container', '0', [cells[i]]);
              }

              temp.push(cells[i]);
            }
          }
        }
      } finally {
        graph.model.endUpdate();
      }

      graph.setSelectionCells(temp);
    }
  }, null, null, Editor.ctrlKey + '+Shift+U');
  /**
   * 从组中移除。
   * 将选中的元素从其所在的组中移除。
   */
  this.addAction('removeFromGroup', function () {
    if (graph.isEnabled()) {
      var cells = graph.getSelectionCells();

      // Removes table rows and cells
      if (cells != null) {
        var temp = [];

        for (var i = 0; i < cells.length; i++) {
          if (!graph.isTableRow(cells[i]) &&
            !graph.isTableCell(cells[i])) {
            temp.push(cells[i]);
          }
        }

        graph.removeCellsFromParent(temp);
      }
    }
  });
  // Adds action
  /**
   * 编辑。
   * 开始编辑当前选中单元格的文本内容。
   */
  this.addAction('edit', function () {
    if (graph.isEnabled()) {
      graph.startEditingAtCell();
    }
  }, null, null, 'F2/Enter');

  /**
   * 编辑资源关联。
   * 编辑当前选中元素的资源关联属性，建立元素与资源的关联关系。
   */
  this.addAction('editRsRel', function () {
    let cell = graph.getSelectionCell();
    let obj = {
      cell: cell,
      graph: graph
    }
    drawioBus.$emit('editRsRelClick', obj);
  });

  /**
   * 编辑数据属性。
   * 打开当前选中元素的属性编辑对话框，修改元素属性和变量。
   */
  this.addAction('editData', function () {
    var cell = graph.getSelectionCell() || graph.getModel().getRoot();
    console.log("editData", cell);
    if (cell.value === '') {
      window.parent.$Message({
        message: '通用图元暂无属性数据！',
        type: 'warning'
      });
      return
    }
    if (cell != null) {
      // ui.showDataDialog(cell);
      editCellAttribute(cell, graph);
    }
  }, null, null, Editor.ctrlKey + '+M');

  /**
   * 添加子页面。
   * 为当前选中的元素添加一个子页面，创建层次化的页面结构。
   */
  this.addAction('addChildPage', function () {
    console.log("addChildPage");
    var cell = graph.getSelectionCell() || graph.getModel().getRoot();
    if (cell != null) {
      // ui.showDataDialog(cell);
      // addChildPage(cell, graph);
      drawioBus.$emit('addChildPageClick', {
        cell,
        graph
      })
    }
  }, null, null);

  /**
   * 显示引用数据。
   * 显示当前选中元素的引用数据详细信息。
   */
  this.addAction('showQuoteData', function () {
    var cell = graph.getSelectionCell() || graph.getModel().getRoot();
    if (cell != null) {
      showCellAttribute(cell, graph);
    }
  }, null, null, null);

  /**
   * 编辑工具提示。
   * 编辑当前选中元素的工具提示文本。
   */
  this.addAction('editTooltip...', function () {
    if (graph.isEnabled() && !graph.isSelectionEmpty()) {
      var cell = graph.getSelectionCell();
      var tooltip = '';

      if (mxUtils.isNode(cell.value)) {
        var tmp = null;

        if (Graph.translateDiagram && Graph.diagramLanguage != null &&
          cell.value.hasAttribute('tooltip_' + Graph.diagramLanguage)) {
          tmp = cell.value.getAttribute('tooltip_' + Graph.diagramLanguage);
        }

        if (tmp == null) {
          tmp = cell.value.getAttribute('tooltip');
        }

        if (tmp != null) {
          tooltip = tmp;
        }
      }

      var dlg = new TextareaDialog(ui, mxResources.get('editTooltip') + ':', tooltip, function (newValue) {
        graph.setTooltipForCell(cell, newValue);
      });
      ui.showDialog(dlg.container, 320, 200, true, true);
      dlg.init();
    }
  }, null, null, 'Alt+Shift+T');
  /**
   * 打开链接。
   * 打开当前选中元素关联的超链接。
   */
  this.addAction('openLink', function () {
    var link = graph.getLinkForCell(graph.getSelectionCell());

    if (link != null) {
      graph.openLink(link);
    }
  });
  /**
   * 编辑链接。
   * 编辑当前选中元素的超链接地址和目标。
   */
  this.addAction('editLink...', function () {
    if (graph.isEnabled() && !graph.isSelectionEmpty()) {
      var cell = graph.getSelectionCell();
      var value = graph.getLinkForCell(cell) || '';

      ui.showLinkDialog(value, mxResources.get('apply'), function (link, docs, linkTarget) {
        link = mxUtils.trim(link);
        graph.setLinkForCell(cell, (link.length > 0) ? link : null);
        graph.setAttributeForCell(cell, 'linkTarget', linkTarget);
      }, true, graph.getLinkTargetForCell(cell));
    }
  }, null, null, 'Alt+Shift+L');
  /**
   * 插入图像。
   * 清除当前选择并调用图像插入功能。
   */
  this.put('insertImage', new Action(mxResources.get('image') + '...', function () {
    if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent())) {
      graph.clearSelection();
      ui.actions.get('image').funct();
    }
  })).isEnabled = isGraphEnabled;
  /**
   * 插入链接。
   * 创建一个新的超链接元素。
   */
  this.put('insertLink', new Action(mxResources.get('link') + '...', function () {
    if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent())) {
      ui.showLinkDialog('', mxResources.get('insert'), function (link, docs, linkTarget) {
        link = mxUtils.trim(link);

        if (link.length > 0) {
          var icon = null;
          var title = graph.getLinkTitle(link);

          if (docs != null && docs.length > 0) {
            icon = docs[0].iconUrl;
            title = docs[0].name || docs[0].type;
            title = title.charAt(0).toUpperCase() + title.substring(1);

            if (title.length > 30) {
              title = title.substring(0, 30) + '...';
            }
          }

          var linkCell = new mxCell(title, new mxGeometry(0, 0, 100, 40),
            'fontColor=#0000EE;fontStyle=4;rounded=1;overflow=hidden;' + ((icon != null) ?
              'shape=label;imageWidth=16;imageHeight=16;spacingLeft=26;align=left;image=' + icon :
              'spacing=10;'));
          linkCell.vertex = true;

          var pt = graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry([linkCell], true));
          linkCell.geometry.x = pt.x;
          linkCell.geometry.y = pt.y;

          graph.setAttributeForCell(linkCell, 'linkTarget', linkTarget);
          graph.setLinkForCell(linkCell, link);
          graph.cellSizeUpdated(linkCell, true);

          graph.getModel().beginUpdate();
          try {
            linkCell = graph.addCell(linkCell);
            graph.fireEvent(new mxEventObject('cellsInserted', 'cells', [linkCell]));
          } finally {
            graph.getModel().endUpdate();
          }

          graph.setSelectionCell(linkCell);
          graph.scrollCellToVisible(graph.getSelectionCell());
        }
      }, true);
    }
  })).isEnabled = isGraphEnabled;
  /**
   * 链接操作。
   * 根据当前状态执行相应的链接操作（插入、编辑或内宵链接）。
   */
  this.addAction('link...', mxUtils.bind(this, function () {
    if (graph.isEnabled()) {
      if (graph.cellEditor.isContentEditing()) {
        var elt = graph.getSelectedElement();
        var link = graph.getParentByName(elt, 'A', graph.cellEditor.textarea);
        var oldValue = '';

        // Workaround for FF returning the outermost selected element after double
        // click on a DOM hierarchy with a link inside (but not as topmost element)
        if (link == null && elt != null && elt.getElementsByTagName != null) {
          // Finds all links in the selected DOM and uses the link
          // where the selection text matches its text content
          var links = elt.getElementsByTagName('a');

          for (var i = 0; i < links.length && link == null; i++) {
            if (links[i].textContent == elt.textContent) {
              link = links[i];
            }
          }
        }

        if (link != null && link.nodeName == 'A') {
          oldValue = link.getAttribute('href') || '';
          graph.selectNode(link);
        }

        var selState = graph.cellEditor.saveSelection();

        ui.showLinkDialog(oldValue, mxResources.get('apply'), mxUtils.bind(this, function (value) {
          graph.cellEditor.restoreSelection(selState);

          if (value != null) {
            graph.insertLink(value);
          }
        }));
      } else if (graph.isSelectionEmpty()) {
        this.get('insertLink').funct();
      } else {
        this.get('editLink').funct();
      }
    }
  })).isEnabled = isGraphEnabled;
  /**
   * 自动调整尺寸。
   * 根据内容自动调整选中元素的尺寸。
   */
  this.addAction('autosize', function () {
    var cells = graph.getSelectionCells();

    if (cells != null) {
      graph.getModel().beginUpdate();
      try {
        for (var i = 0; i < cells.length; i++) {
          var cell = cells[i];

          if (graph.getModel().getChildCount(cell)) {
            graph.updateGroupBounds([cell], 20);
          } else {
            var state = graph.view.getState(cell);
            var geo = graph.getCellGeometry(cell);

            if (graph.getModel().isVertex(cell) && state != null && state.text != null &&
              geo != null && graph.isWrapping(cell)) {
              geo = geo.clone();
              geo.height = state.text.boundingBox.height / graph.view.scale;
              graph.getModel().setGeometry(cell, geo);
            } else {
              graph.updateCellSize(cell);
            }
          }
        }
      } finally {
        graph.getModel().endUpdate();
      }
    }
  }, null, null, Editor.ctrlKey + '+Shift+Y');
  /**
   * 切换格式化文本。
   * 在HTML格式和纯文本格式之间切换元素的文本显示方式。
   */
  this.addAction('formattedText', function () {
    var refState = graph.getView().getState(graph.getSelectionCell());

    if (refState != null) {
      graph.stopEditing();
      var value = (refState.style['html'] == '1') ? null : '1';

      graph.getModel().beginUpdate();
      try {
        var cells = graph.getSelectionCells();

        for (var i = 0; i < cells.length; i++) {
          state = graph.getView().getState(cells[i]);

          if (state != null) {
            var html = mxUtils.getValue(state.style, 'html', '0');

            if (html == '1' && value == null) {
              var label = graph.convertValueToString(state.cell);

              if (mxUtils.getValue(state.style, 'nl2Br', '1') != '0') {
                // Removes newlines from HTML and converts breaks to newlines
                // to match the HTML output in plain text
                label = label.replace(/\n/g, '').replace(/<br\s*.?>/g, '\n');
              }

              // Removes HTML tags
              var temp = document.createElement('div');
              temp.innerHTML = graph.sanitizeHtml(label);
              label = mxUtils.extractTextWithWhitespace(temp.childNodes);

              graph.cellLabelChanged(state.cell, label);
              graph.setCellStyles('html', value, [cells[i]]);
            } else if (html == '0' && value == '1') {
              // Converts HTML tags to text
              var label = mxUtils.htmlEntities(graph.convertValueToString(state.cell), false);

              if (mxUtils.getValue(state.style, 'nl2Br', '1') != '0') {
                // Converts newlines in plain text to breaks in HTML
                // to match the plain text output
                label = label.replace(/\n/g, '<br/>');
              }

              graph.cellLabelChanged(state.cell, graph.sanitizeHtml(label));
              graph.setCellStyles('html', value, [cells[i]]);
            }
          }
        }

        ui.fireEvent(new mxEventObject('styleChanged', 'keys', ['html'],
          'values', [(value != null) ? value : '0'], 'cells', cells));
      } finally {
        graph.getModel().endUpdate();
      }
    }
  });
  /**
   * 切换文本换行。
   * 切换选中元素文本的换行模式。
   */
  this.addAction('wordWrap', function () {
    var state = graph.getView().getState(graph.getSelectionCell());
    var value = 'wrap';

    graph.stopEditing();

    if (state != null && state.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap') {
      value = null;
    }

    graph.setCellStyles(mxConstants.STYLE_WHITE_SPACE, value);
  });
  /**
   * 设置旋转角度。
   * 打开对话框设置选中元素的旋转角度（0-360度）。
   */
  this.addAction('rotation', function () {
    var value = '0';
    var state = graph.getView().getState(graph.getSelectionCell());

    if (state != null) {
      value = state.style[mxConstants.STYLE_ROTATION] || value;
    }

    var dlg = new FilenameDialog(ui, value, mxResources.get('apply'), function (newValue) {
      if (newValue != null && newValue.length > 0) {
        graph.setCellStyles(mxConstants.STYLE_ROTATION, newValue);
      }
    }, mxResources.get('enterValue') + ' (' + mxResources.get('rotation') + ' 0-360)');

    ui.showDialog(dlg.container, 375, 80, true, true);
    dlg.init();
  });
  // View actions
  /**
   * 重置视图。
   * 将缩放比例重置为100%，并重置滚动条位置。
   */
  this.addAction('resetView', function () {
    graph.zoomTo(1);
    ui.resetScrollbars();
  }, null, null, 'Home');
  /**
   * 放大。
   * 放大视图，提高缩放级别以查看更多细节。
   */
  this.addAction('zoomIn', function (evt) {
    if (graph.isFastZoomEnabled()) {
      graph.lazyZoom(true, true, ui.buttonZoomDelay);
    } else {
      graph.zoomIn();
    }
  }, null, null, Editor.ctrlKey + ' + (Numpad) / Alt+Mousewheel');
  /**
   * 缩小。
   * 缩小视图，降低缩放级别以查看更大范围。
   */
  this.addAction('zoomOut', function (evt) {
    if (graph.isFastZoomEnabled()) {
      graph.lazyZoom(false, true, ui.buttonZoomDelay);
    } else {
      graph.zoomOut();
    }
  }, null, null, Editor.ctrlKey + ' - (Numpad) / Alt+Mousewheel');
  /**
   * 适合窗口。
   * 调整缩放级别使整个绘图内容适合在当前窗口中显示。
   */
  this.addAction('fitWindow', function () {
    // 设置多次相同缩放比例后 graphBounds 有问题，先重置到1，再缩放
    var resetView = ui.actions.get('resetView');
    resetView.funct();
    var bounds = graph.getGraphBounds()
    var t = graph.view.translate;
    var s = graph.view.scale;

    bounds.x = bounds.x / s - t.x;
    bounds.y = bounds.y / s - t.y;
    bounds.width /= s;
    bounds.height /= s;

    if (graph.backgroundImage != null) {
      bounds.add(new mxRectangle(0, 0, graph.backgroundImage.width, graph.backgroundImage.height));
    }

    if (bounds.width == 0 || bounds.height == 0) {
      graph.zoomTo(1);
      ui.resetScrollbars();
    } else {
      graph.fitWindow(bounds);
    }
  }, null, null, Editor.ctrlKey + '+Shift+H');
  /**
   * 适合页面。
   * 调整缩放级别使整个页面在窗口中完整显示。
   */
  this.addAction('fitPage', mxUtils.bind(this, function () {
    if (!graph.pageVisible) {
      this.get('pageView').funct();
    }

    var fmt = graph.pageFormat;
    var ps = graph.pageScale;
    var cw = graph.container.clientWidth - 10;
    var ch = graph.container.clientHeight - 10;
    var scale = Math.floor(20 * Math.min(cw / fmt.width / ps, ch / fmt.height / ps)) / 20;
    graph.zoomTo(scale);

    if (mxUtils.hasScrollbars(graph.container)) {
      var pad = graph.getPagePadding();
      graph.container.scrollTop = pad.y * graph.view.scale - 1;
      graph.container.scrollLeft = Math.min(pad.x * graph.view.scale, (graph.container.scrollWidth - graph.container.clientWidth) / 2) - 1;
    }
  }), null, null, Editor.ctrlKey + '+J');
  /**
   * 适合两页。
   * 调整缩放级别使两个页面并排在窗口中显示。
   */
  this.addAction('fitTwoPages', mxUtils.bind(this, function () {
    if (!graph.pageVisible) {
      this.get('pageView').funct();
    }

    var fmt = graph.pageFormat;
    var ps = graph.pageScale;
    var cw = graph.container.clientWidth - 10;
    var ch = graph.container.clientHeight - 10;

    var scale = Math.floor(20 * Math.min(cw / (2 * fmt.width) / ps, ch / fmt.height / ps)) / 20;
    graph.zoomTo(scale);

    if (mxUtils.hasScrollbars(graph.container)) {
      var pad = graph.getPagePadding();
      graph.container.scrollTop = Math.min(pad.y, (graph.container.scrollHeight - graph.container.clientHeight) / 2);
      graph.container.scrollLeft = Math.min(pad.x, (graph.container.scrollWidth - graph.container.clientWidth) / 2);
    }
  }), null, null, Editor.ctrlKey + '+Shift+J');
  /**
   * 适合页面宽度。
   * 调整缩放级别使页面宽度全部在窗口中显示。
   */
  this.addAction('fitPageWidth', mxUtils.bind(this, function () {
    if (!graph.pageVisible) {
      this.get('pageView').funct();
    }

    var fmt = graph.pageFormat;
    var ps = graph.pageScale;
    var cw = graph.container.clientWidth - 10;

    var scale = Math.floor(20 * cw / fmt.width / ps) / 20;
    graph.zoomTo(scale);

    if (mxUtils.hasScrollbars(graph.container)) {
      var pad = graph.getPagePadding();
      graph.container.scrollLeft = Math.min(pad.x * graph.view.scale,
        (graph.container.scrollWidth - graph.container.clientWidth) / 2);
    }
  }));
  /**
   * 自定义缩放。
   * 打开对话框输入自定义的缩放比例。
   */
  this.put('customZoom', new Action(mxResources.get('custom') + '...', mxUtils.bind(this, function () {
    var dlg = new FilenameDialog(this.editorUi, parseInt(graph.getView().getScale() * 100), mxResources.get('apply'), mxUtils.bind(this, function (newValue) {
      var val = parseInt(newValue);

      if (!isNaN(val) && val > 0) {
        graph.zoomTo(val / 100);
      }
    }), mxResources.get('zoom') + ' (%)');
    this.editorUi.showDialog(dlg.container, 300, 80, true, true);
    dlg.init();
  }), null, null, Editor.ctrlKey + '+0'));
  /**
   * 页面缩放设置。
   * 打开对话框设置页面缩放比例。
   */
  this.addAction('pageScale...', mxUtils.bind(this, function () {
    var dlg = new FilenameDialog(this.editorUi, parseInt(graph.pageScale * 100), mxResources.get('apply'), mxUtils.bind(this, function (newValue) {
      var val = parseInt(newValue);

      if (!isNaN(val) && val > 0) {
        var change = new ChangePageSetup(ui, null, null, null, val / 100);
        change.ignoreColor = true;
        change.ignoreImage = true;

        graph.model.execute(change);
      }
    }), mxResources.get('pageScale') + ' (%)');
    this.editorUi.showDialog(dlg.container, 300, 80, true, true);
    dlg.init();
  }));

  // Option actions
  var action = null;
  /**
   * 切换网格。
   * 切换背景网格的显示和隐藏状态。
   */
  action = this.addAction('grid', function () {
    graph.setGridEnabled(!graph.isGridEnabled());
    ui.fireEvent(new mxEventObject('gridEnabledChanged'));
  }, null, null, Editor.ctrlKey + '+Shift+G');
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.isGridEnabled();
  });
  action.setEnabled(false);

  /**
   * 切换参考线。
   * 切换拖拽时的对齐参考线显示。
   */
  action = this.addAction('guides', function () {
    graph.graphHandler.guidesEnabled = !graph.graphHandler.guidesEnabled;
    ui.fireEvent(new mxEventObject('guidesEnabledChanged'));
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.graphHandler.guidesEnabled;
  });
  action.setEnabled(false);

  /**
   * 切换工具提示。
   * 切换鼠标悬停时工具提示的显示。
   */
  action = this.addAction('tooltips', function () {
    graph.tooltipHandler.setEnabled(!graph.tooltipHandler.isEnabled());
    ui.fireEvent(new mxEventObject('tooltipsEnabledChanged'));
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.tooltipHandler.isEnabled();
  });

  /**
   * 切换折叠功能。
   * 切换图形元素的折叠展开功能。
   */
  action = this.addAction('collapseExpand', function () {
    var change = new ChangePageSetup(ui);
    change.ignoreColor = true;
    change.ignoreImage = true;
    change.foldingEnabled = !graph.foldingEnabled;

    graph.model.execute(change);
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.foldingEnabled;
  });
  action.isEnabled = isGraphEnabled;
  /**
   * 切换滚动条。
   * 切换编辑器滚动条的显示和隐藏。
   */
  action = this.addAction('scrollbars', function () {
    ui.setScrollbars(!ui.hasScrollbars());
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.scrollbars;
  });
  /**
   * 切换页面视图。
   * 在页面模式和正常模式之间切换。
   */
  action = this.addAction('pageView', mxUtils.bind(this, function () {
    ui.setPageVisible(!graph.pageVisible);
  }));
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.pageVisible;
  });
  /**
   * 切换连接箭头。
   * 切换连线操作时箭头指示的显示。
   */
  action = this.addAction('connectionArrows', function () {
    graph.connectionArrowsEnabled = !graph.connectionArrowsEnabled;
    ui.fireEvent(new mxEventObject('connectionArrowsChanged'));
  }, null, null, 'Alt+Shift+A');
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.connectionArrowsEnabled;
  });
  /**
   * 切换连接点。
   * 切换元素连接点的显示和隐藏。
   */
  action = this.addAction('connectionPoints', function (enabled) {
    if (enabled === true || enabled === false) {
      graph.setConnectable(enabled);
    } else {
      graph.setConnectable(!graph.connectionHandler.isEnabled());
    }
    ui.fireEvent(new mxEventObject('connectionPointsChanged'));
  }, null, null, 'Alt+Shift+P');
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.connectionHandler.isEnabled();
  });
  /**
   * 切换复制连接。
   * 切换是否在连接时自动创建目标元素。
   */
  action = this.addAction('copyConnect', function () {
    graph.connectionHandler.setCreateTarget(!graph.connectionHandler.isCreateTarget());
    ui.fireEvent(new mxEventObject('copyConnectChanged'));
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.connectionHandler.isCreateTarget();
  });
  action.isEnabled = isGraphEnabled;
  /**
   * 切换自动保存。
   * 切换编辑器的自动保存功能。
   */
  action = this.addAction('autosave', function () {
    ui.editor.setAutosave(!ui.editor.autosave);
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return ui.editor.autosave;
  });
  action.isEnabled = isGraphEnabled;
  action.visible = false;

  // Help actions
  /**
   * 帮助。
   * 打开在线帮助文档，显示使用说明和指南。
   */
  this.addAction('help', function () {
    var ext = '';

    if (mxResources.isLanguageSupported(mxClient.language)) {
      ext = '_' + mxClient.language;
    }

    graph.openLink(RESOURCES_PATH + '/help' + ext + '.html');
  });

  var showingAbout = false;

  /**
   * 关于对话框。
   * 显示应用程序的关于信息和版本详情。
   */
  this.put('about', new Action(mxResources.get('about') + ' Graph Editor...', function () {
    if (!showingAbout) {
      ui.showDialog(new AboutDialog(ui).container, 320, 280, true, true, function () {
        showingAbout = false;
      });

      showingAbout = true;
    }
  }));

  // Font style actions
  var toggleFontStyle = mxUtils.bind(this, function (key, style, fn, shortcut) {
    return this.addAction(key, function () {
      if (fn != null && graph.cellEditor.isContentEditing()) {
        fn();
      } else {
        graph.stopEditing(false);

        graph.getModel().beginUpdate();
        try {
          var cells = graph.getSelectionCells();
          graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, style, cells);

          // Removes bold and italic tags and CSS styles inside labels
          if ((style & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
            graph.updateLabelElements(graph.getSelectionCells(), function (elt) {
              elt.style.fontWeight = null;

              if (elt.nodeName == 'B') {
                graph.replaceElement(elt);
              }
            });
          } else if ((style & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
            graph.updateLabelElements(graph.getSelectionCells(), function (elt) {
              elt.style.fontStyle = null;

              if (elt.nodeName == 'I') {
                graph.replaceElement(elt);
              }
            });
          } else if ((style & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
            graph.updateLabelElements(graph.getSelectionCells(), function (elt) {
              elt.style.textDecoration = null;

              if (elt.nodeName == 'U') {
                graph.replaceElement(elt);
              }
            });
          }

          for (var i = 0; i < cells.length; i++) {
            if (graph.model.getChildCount(cells[i]) == 0) {
              graph.autoSizeCell(cells[i], false);
            }
          }
        } finally {
          graph.getModel().endUpdate();
        }
      }
    }, null, null, shortcut);
  });

  /**
   * 加粗字体。
   * 切换选中文本的粗体样式。
   */
  toggleFontStyle('bold', mxConstants.FONT_BOLD, function () {
    document.execCommand('bold', false, null);
  }, Editor.ctrlKey + '+B');

  /**
   * 斜体字体。
   * 切换选中文本的斜体样式。
   */
  toggleFontStyle('italic', mxConstants.FONT_ITALIC, function () {
    document.execCommand('italic', false, null);
  }, Editor.ctrlKey + '+I');

  /**
   * 下划线。
   * 切换选中文本的下划线样式。
   */
  toggleFontStyle('underline', mxConstants.FONT_UNDERLINE, function () {
    document.execCommand('underline', false, null);
  }, Editor.ctrlKey + '+U');

  // Color actions
  /**
   * 设置字体颜色。
   * 打开颜色选择器设置文本字体颜色。
   */
  this.addAction('fontColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_FONTCOLOR, 'forecolor', '000000');
  });
  /**
   * 设置描边颜色。
   * 打开颜色选择器设置元素的边框颜色。
   */
  this.addAction('strokeColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_STROKECOLOR);
  });
  /**
   * 设置填充颜色。
   * 打开颜色选择器设置元素的填充颜色。
   */
  this.addAction('fillColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_FILLCOLOR);
  });
  /**
   * 设置渐变颜色。
   * 打开颜色选择器设置元素的渐变颜色。
   */
  this.addAction('gradientColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_GRADIENTCOLOR);
  });
  /**
   * 设置背景颜色。
   * 打开颜色选择器设置文本标签的背景颜色。
   */
  this.addAction('backgroundColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, 'backcolor');
  });
  /**
   * 设置边框颜色。
   * 打开颜色选择器设置文本标签的边框颜色。
   */
  this.addAction('borderColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_LABEL_BORDERCOLOR);
  });

  // Format actions
  /**
   * 垂直布局。
   * 将元素布局方向设置为垂直方向。
   */
  this.addAction('vertical', function () {
    ui.menus.toggleStyle(mxConstants.STYLE_HORIZONTAL, true);
  });
  /**
   * 切换阴影。
   * 切换选中元素的阴影效果。
   */
  this.addAction('shadow', function () {
    ui.menus.toggleStyle(mxConstants.STYLE_SHADOW);
  });
  /**
   * 实线样式。
   * 将选中元素的边框设置为实线样式。
   */
  this.addAction('solid', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_DASHED, null);
      graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, null);
      ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
        'values', [null, null], 'cells', graph.getSelectionCells()));
    } finally {
      graph.getModel().endUpdate();
    }
  });
  /**
   * 虚线样式。
   * 将选中元素的边框设置为虚线样式。
   */
  this.addAction('dashed', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_DASHED, '1');
      graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, null);
      ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
        'values', ['1', null], 'cells', graph.getSelectionCells()));
    } finally {
      graph.getModel().endUpdate();
    }
  });
  /**
   * 点线样式。
   * 将选中元素的边框设置为点线样式。
   */
  this.addAction('dotted', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_DASHED, '1');
      graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, '1 4');
      ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
        'values', ['1', '1 4'], 'cells', graph.getSelectionCells()));
    } finally {
      graph.getModel().endUpdate();
    }
  });
  /**
   * 尖角样式。
   * 将选中元素设置为尖角样式（非圆角非曲线）。
   */
  this.addAction('sharp', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_ROUNDED, '0');
      graph.setCellStyles(mxConstants.STYLE_CURVED, '0');
      ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
        'values', ['0', '0'], 'cells', graph.getSelectionCells()));
    } finally {
      graph.getModel().endUpdate();
    }
  });
  /**
   * 圆角样式。
   * 将选中元素设置为圆角样式。
   */
  this.addAction('rounded', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_ROUNDED, '1');
      graph.setCellStyles(mxConstants.STYLE_CURVED, '0');
      ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
        'values', ['1', '0'], 'cells', graph.getSelectionCells()));
    } finally {
      graph.getModel().endUpdate();
    }
  });
  /**
   * 切换圆角样式。
   * 在圆角和尖角之间切换选中元素的样式。
   */
  this.addAction('toggleRounded', function () {
    if (!graph.isSelectionEmpty() && graph.isEnabled()) {
      graph.getModel().beginUpdate();
      try {
        var cells = graph.getSelectionCells();
        var style = graph.getCurrentCellStyle(cells[0]);
        var value = (mxUtils.getValue(style, mxConstants.STYLE_ROUNDED, '0') == '1') ? '0' : '1';

        graph.setCellStyles(mxConstants.STYLE_ROUNDED, value);
        graph.setCellStyles(mxConstants.STYLE_CURVED, null);
        ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
          'values', [value, '0'], 'cells', graph.getSelectionCells()));
      } finally {
        graph.getModel().endUpdate();
      }
    }
  });
  /**
   * 曲线样式。
   * 将选中元素设置为曲线样式。
   */
  this.addAction('curved', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_ROUNDED, '0');
      graph.setCellStyles(mxConstants.STYLE_CURVED, '1');
      ui.fireEvent(new mxEventObject('styleChanged', 'keys', [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
        'values', ['0', '1'], 'cells', graph.getSelectionCells()));
    } finally {
      graph.getModel().endUpdate();
    }
  });
  /**
   * 切换可折叠状态。
   * 设置选中元素是否可以折叠展开。
   */
  this.addAction('collapsible', function () {
    var state = graph.view.getState(graph.getSelectionCell());
    var value = '1';

    if (state != null && graph.getFoldingImage(state) != null) {
      value = '0';
    }

    graph.setCellStyles('collapsible', value);
    ui.fireEvent(new mxEventObject('styleChanged', 'keys', ['collapsible'],
      'values', [value], 'cells', graph.getSelectionCells()));
  });
  /**
   * 编辑样式代码。
   * 打开文本编辑对话框直接编辑元素的CSS样式代码。
   */
  this.addAction('editStyle...', mxUtils.bind(this, function () {
    var cells = graph.getSelectionCells();

    if (cells != null && cells.length > 0) {
      var model = graph.getModel();

      var dlg = new TextareaDialog(this.editorUi, mxResources.get('editStyle') + ':',
        model.getStyle(cells[0]) || '',
        function (newValue) {
          if (newValue != null) {
            graph.setCellStyle(mxUtils.trim(newValue), cells);
          }
        }, null, null, 400, 220);
      this.editorUi.showDialog(dlg.container, 420, 300, true, true);
      dlg.init();
    }
  }), null, null, Editor.ctrlKey + '+E');
  // 把该cell的样式设置为默认样式
  /**
   * 设为默认样式。
   * 将当前选中元素的样式设置为新元素的默认样式。
   */
  this.addAction('setAsDefaultStyle', function () {
    if (graph.isEnabled() && graph.getSelectionCount() === 1) {
      ui.setAsDefaultStyle(graph.getSelectionCell());
    }
  }, null, null, Editor.ctrlKey + '+Shift+D');

  /**
   * 清除默认样式。
   * 清除当前选中元素的默认样式设置。
   */
  this.addAction('clearAsDefaultStyle', function () {
    if (graph.isEnabled() && graph.getSelectionCount() === 1) {
      graph.clearDefaultStyle(graph.getSelectionCell());
    }
  }, null, null, );

  // 设置默认样式
  this.addAction('setDefaultStyle', async function () {
    if (graph.isEnabled() && !graph.isSelectionEmpty()) {
      const cells = graph.getSelectionCells();
      const CacheMap = myEditor.sidebar.cellAndStyleMap.reduce((acc, cur) => (acc[cur.metaId] = cur, acc), {})
      try {
        graph.getModel().beginUpdate();
        await Promise.all(cells.map(async cell => {
          const metaId = cell.value ? .getAttribute('metaId')
          if (!metaId) return
          const cache = CacheMap[metaId]
          if (!cache) return;
          const isImage = cell.style ? .includes('shape=image')
          graph.model.setStyle(cell, cache.style)
          const geo = cell.geometry.clone()
          geo.width = cache.width
          geo.height = cache.height
          graph.model.setGeometry(cell, geo);
          if (isImage) {
            graph.model.setAttribute(cell, 'imageId', null)
            graph.model.setAttribute(cell, 'categoryId', null)
          }
          // 编辑图片后恢复图片异常
          let label
          // 富文本格式
          if (cell.style ? .includes('html=1')) {
            const name = cell.value.getAttribute('name')
            await new utils().putLabel(cell, name, resolveCell(cell))
            label = cell.value.getAttribute('label')
            // 图片模式label就是name
            if (isImage) {
              cell.value.setAttribute('label', name)
            }
          } else {
            label = cell.value.getAttribute('name')
          }
          // 触发撤销回滚记录
          graph.model.setAttribute(cell, 'label', label)
        }))
      } finally {
        graph.getModel().endUpdate();
      }
    }
  }, null, null)
  /**
   * 查看交互清单。
   * 显示与当前选中元素相关的交互清单数据。
   */
  this.addAction('viewTable', function (data) {
    drawioBus.$emit('viewTableEvent', data)
  }, null, null)
  /**
   * 清除默认样式。
   * 清除当前选中元素的默认样式设置。
   */
  this.addAction('clearDefaultStyle', function () {
    if (graph.isEnabled()) {
      graph.clearDefaultStyle(graph.getSelectionCell())
      // ui.clearDefaultStyle();
    }
  }, null, null, Editor.ctrlKey + '+Shift+R');
  /**
   * 添加航点。
   * 在选中的连线上添加一个新的路径点，用于调整连线形状。
   */
  this.addAction('addWaypoint', function () {
    var cell = graph.getSelectionCell();

    if (cell != null && graph.getModel().isEdge(cell)) {
      var handler = editor.graph.selectionCellsHandler.getHandler(cell);

      if (handler instanceof mxEdgeHandler) {
        var t = graph.view.translate;
        var s = graph.view.scale;
        var dx = t.x;
        var dy = t.y;

        var parent = graph.getModel().getParent(cell);
        var pgeo = graph.getCellGeometry(parent);

        while (graph.getModel().isVertex(parent) && pgeo != null) {
          dx += pgeo.x;
          dy += pgeo.y;

          parent = graph.getModel().getParent(parent);
          pgeo = graph.getCellGeometry(parent);
        }

        var x = Math.round(graph.snap(graph.popupMenuHandler.triggerX / s - dx));
        var y = Math.round(graph.snap(graph.popupMenuHandler.triggerY / s - dy));

        handler.addPointAt(handler.state, x, y);
      }
    }
  });
  /**
   * 移除航点。
   * 移除连线上的指定航点。
   */
  this.addAction('removeWaypoint', function () {
    // TODO: Action should run with "this" set to action
    var rmWaypointAction = ui.actions.get('removeWaypoint');

    if (rmWaypointAction.handler != null) {
      // NOTE: Popupevent handled and action updated in Menus.createPopupMenu
      rmWaypointAction.handler.removePoint(rmWaypointAction.handler.state, rmWaypointAction.index);
    }
  });
  /**
   * 清除所有航点。
   * 移除选中连线上的所有路径点，使连线变成直线。
   */
  this.addAction('clearWaypoints', function () {
    var cells = graph.getSelectionCells();

    if (cells != null) {
      cells = graph.addAllEdges(cells);

      graph.getModel().beginUpdate();
      try {
        for (var i = 0; i < cells.length; i++) {
          var cell = cells[i];

          if (graph.getModel().isEdge(cell)) {
            var geo = graph.getCellGeometry(cell);

            if (geo != null) {
              geo = geo.clone();
              geo.points = null;
              graph.getModel().setGeometry(cell, geo);
            }
          }
        }
      } finally {
        graph.getModel().endUpdate();
      }
    }
  }, null, null, 'Alt+Shift+C');
  /**
   * 下标格式。
   * 将选中文本设置为下标格式。
   */
  action = this.addAction('subscript', mxUtils.bind(this, function () {
    if (graph.cellEditor.isContentEditing()) {
      document.execCommand('subscript', false, null);
    }
  }), null, null, Editor.ctrlKey + '+,');
  /**
   * 上标格式。
   * 将选中文本设置为上标格式。
   */
  action = this.addAction('superscript', mxUtils.bind(this, function () {
    if (graph.cellEditor.isContentEditing()) {
      document.execCommand('superscript', false, null);
    }
  }), null, null, Editor.ctrlKey + '+.');
  /**
   * 缩进文本。
   * 在文本编辑模式下增加文本缩进。
   */
  action = this.addAction('indent', mxUtils.bind(this, function () {
    // NOTE: Alt+Tab for outdent implemented via special code in
    // keyHandler.getFunction in EditorUi.js. Ctrl+Tab is reserved.
    if (graph.cellEditor.isContentEditing()) {
      document.execCommand('indent', false, null);
    }
  }), null, null, 'Shift+Tab');
  /**
   * 插入图片。
   * 打开图片选择对话框，将图片插入到当前编辑位置或创建新的图片元素。
   */
  this.addAction('image...', function () {
    if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent())) {
      var state = graph.getView().getState(graph.getSelectionCell());

      var selectionState = graph.cellEditor.saveSelection();

      var img = new Image();

      img.onload = function () {
        var newValue = img.src;
        var w = img.width;
        var h = img.height;

        // Inserts image into HTML text
        if (graph.cellEditor.isContentEditing()) {
          graph.cellEditor.restoreSelection(selectionState);
          graph.insertImage(newValue, w, h);
        } else {
          var cells = graph.getSelectionCells();

          if (newValue != null) {
            var select = null;

            graph.getModel().beginUpdate();
            try {
              // Inserts new cell if no cell is selected
              if (cells.length == 0) {
                var pt = graph.getInsertPoint();
                cells = [graph.insertVertex(graph.getDefaultParent(), null, '', pt.x, pt.y, w, h,
                  'shape=image;verticalLabelPosition=bottom;verticalAlign=top;')];
                select = cells;
              }

              graph.setCellStyles(mxConstants.STYLE_IMAGE, newValue, cells);

              // Sets shape only if not already shape with image (label or image)
              var state = graph.view.getState(cells[0]);
              var style = (state != null) ? state.style : graph.getCellStyle(cells[0]);

              if (style[mxConstants.STYLE_SHAPE] != 'image') {
                graph.setCellStyles(mxConstants.STYLE_SHAPE, 'image', cells);
              }

              if (graph.getSelectionCount()) {
                if (w != null && h != null) {
                  cells.forEach(mxUtils.bind(this, function (cell) {
                    var geo = graph.getModel().getGeometry(cell);

                    if (geo != null) {
                      geo = geo.clone();
                      const max = 120
                      if (w > h) {
                        geo.width = max;
                        geo.height = max / (w / h);
                      } else {
                        geo.height = max;
                        geo.width = max / (h / w);
                      }
                      graph.getModel().setGeometry(cell, geo);
                    }
                  }))
                }
              }
            } finally {
              graph.getModel().endUpdate();
            }

            if (select != null) {
              graph.setSelectionCells(select);
              graph.scrollCellToVisible(select[0]);
            }
          }
        }
      };

      img.onerror = function () {
        mxUtils.alert(mxResources.get('fileNotFound'));
      };
      changeBackgroundImg(img, state);
    }

  }).isEnabled = isGraphEnabled;
  /**
   * 显示隐藏图层面板。
   * 切换图层管理窗口的显示状态，用于管理不同层的元素。
   */
  action = this.addAction('layers', mxUtils.bind(this, function () {
    if (this.layersWindow == null) {
      // LATER: Check outline window for initial placement
      this.layersWindow = new LayersWindow(ui, document.body.offsetWidth - 280, 120, 220, 196);
      this.layersWindow.window.addListener('show', function () {
        ui.fireEvent(new mxEventObject('layers'));
      });
      this.layersWindow.window.addListener('hide', function () {
        ui.fireEvent(new mxEventObject('layers'));
      });
      this.layersWindow.window.setVisible(true);
      ui.fireEvent(new mxEventObject('layers'));

      this.layersWindow.init();
    } else {
      this.layersWindow.window.setVisible(!this.layersWindow.window.isVisible());
    }
  }), null, null, Editor.ctrlKey + '+Shift+L');
  action.visible = false
  action.setToggleAction(true);
  action.setSelectedCallback(mxUtils.bind(this, function () {
    return this.layersWindow != null && this.layersWindow.window.isVisible();
  }));
  // 插入本地图片
  /**
   * 插入本地图片。
   * 打开文件选择对话框，上传并插入本地图片文件。
   */
  action = this.addAction('insertLocalImage', mxUtils.bind(this, function () {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = '.png,.jpg,.svg,.bmp';
    input.click();
    const cell = graph.getSelectionCell();
    input.addEventListener('change', (event) => {
      var files = event.target.files;
      var file = files.item(0);
      var formData = new window.top.FormData();
      formData.append('upload', file);

      window.$axios.post(contextPath + '/design/uploadImage', formData).then(res => {
        if (res.code != 200) return;
        graph.getModel().beginUpdate();
        try {
          graph.getModel().setAttribute(cell, 'label', '');
          graph.setCellStyles(mxConstants.STYLE_IMAGE, res.data.url, [cell]);
          graph.setCellStyles(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_IMAGE, [cell]);
        } finally {
          graph.getModel().endUpdate();
        }
      })
    })

  }), null, null);
  // 格式面板
  /**
   * 显示隐藏格式面板。
   * 切换右侧格式设置面板的显示状态。
   */
  action = this.addAction('formatPanel', mxUtils.bind(this, function () {
    ui.toggleFormatPanel();
  }), null, null, Editor.ctrlKey + '+Shift+P');
  action.setToggleAction(true);
  action.setSelectedCallback(mxUtils.bind(this, function () {
    return ui.formatWidth > 0;
  }));
  /**
   * 显示隐藏缩略图。
   * 切换缩略图窗口的显示状态，用于快速导航和全局视图。
   */
  action = this.addAction('outline', mxUtils.bind(this, function () {
    if (this.outlineWindow == null) {
      // LATER: Check layers window for initial placement
      this.outlineWindow = new OutlineWindow(ui, document.body.offsetWidth - 260, 100, 180, 180);
      this.outlineWindow.window.addListener('show', function () {
        ui.fireEvent(new mxEventObject('outline'));
      });
      this.outlineWindow.window.addListener('hide', function () {
        ui.fireEvent(new mxEventObject('outline'));
      });
      this.outlineWindow.window.setVisible(true);
      ui.fireEvent(new mxEventObject('outline'));
    } else {
      this.outlineWindow.window.setVisible(!this.outlineWindow.window.isVisible());
    }
  }), null, null, Editor.ctrlKey + '+Shift+O');

  action.setToggleAction(true);
  action.setSelectedCallback(mxUtils.bind(this, function () {
    return this.outlineWindow != null && this.outlineWindow.window.isVisible();
  }));

  /**
   * 格式刷。
   * 复制选中元素的样式格式，然后可以应用到其他元素上。
   */
  this.addAction('painter', function () {
    const elt = document.getElementById('gesprite-painter')
    if (graph.painterState) {
      elt.style.background = null
      graph.painterState = null
      return
    } else {
      elt.style.background = '#f8c382'
    }
    /**
     * 格式刷要同步的
     * fontColor
     * fontSize
     * fontFamily
     * fontStyle
     * horizontal
     * verticalAlign
     * align
     * labelBackgroundColor
     * labelBorderColor
     * labelPosition
     * verticalLabelPosition
     * textDirection
     * gradientColor
     * fillColor
     * strokeColor
     * strokeWidth
     */
    // 需要复用的样式
    const painterStyleString = 'fontColor, fontSize, fontFamily, fontStyle, horizontal, verticalAlign, align, labelBackgroundColor, labelBorderColor, labelPosition, verticalLabelPosition, textDirection, gradientColor, fillColor, strokeColor, strokeWidth'

    const painterStyleList = Array.from(new Set(painterStyleString.split(',')))

    const pick = (obj, keys) => {
      const result = {}
      keys.forEach(key => {
        key = key.trim()
        result[key] = obj[key]
      })
      return result
    }

    const cell = graph.getSelectionCell()

    const getPainterStyle = (cell) => {
      const cellState = graph.view.getState(cell)
      const painterStyle = pick(cellState.style, painterStyleList)
      return {
        cell,
        painterStyle
      }
    }
    graph.painterState = getPainterStyle(cell)

    // 如果是组，记录它的所有children
    if (cell.style.includes('group')) {
      graph.painterState.children = cell.children.map(item => {
        return getPainterStyle(item)
      })
    }
  }, null, null, null).isEnabled = isGraphEnabled;
};

/**
 * 在指定名称下注册给定的操作。
 * 创建一个新的Action对象并将其注册到操作映射表中。
 * 
 * @param {string} key - 操作的键名
 * @param {Function} funct - 要执行的函数
 * @param {boolean} enabled - 是否启用该操作
 * @param {string} iconCls - 图标类名
 * @param {string} shortcut - 快捷键
 * @returns {Action} 创建的操作对象
 */
Actions.prototype.addAction = function (key, funct, enabled, iconCls, shortcut) {
  var title;

  if (key.substring(key.length - 3) == '...') {
    key = key.substring(0, key.length - 3);
    title = mxResources.get(key) + '...';
  } else {
    title = mxResources.get(key);
  }

  return this.put(key, new Action(title, funct, enabled, iconCls, shortcut));
};

/**
 * 注册一个操作按钮。
 * 创建一个新的操作按钮并将其注册到操作映射表中。
 * 
 * @param {string} key - 操作的键名
 * @param {Function} funct - 要执行的函数
 * @param {boolean} enabled - 是否启用该操作
 * @param {string} iconCls - 图标类名
 * @param {string} shortcut - 显示文本
 * @returns {Action} 创建的操作对象
 */
Actions.prototype.addActionButton = function (key, funct, enabled, iconCls, shortcut) {
  var title = shortcut;
  return this.put(key, new Action(title, funct, enabled, iconCls, shortcut));
};

/**
 * 在指定名称下注册给定的操作。
 * 将操作对象存储到actions映射表中。
 * 
 * @param {string} name - 操作的名称
 * @param {Action} action - 要注册的操作对象
 * @returns {Action} 注册的操作对象
 */
Actions.prototype.put = function (name, action) {
  this.actions[name] = action;

  return action;
};

/**
 * 返回指定名称的操作，如果不存在则返回null。
 * 从 actions映射表中获取指定的操作对象。
 * 
 * @param {string} name - 操作的名称
 * @returns {Action|null} 找到的操作对象或null
 */
Actions.prototype.get = function (name) {
  return this.actions[name];
};

/**
 * 为给定参数构造一个新的操作。
 * 创建一个新的Action对象，用于表示用户可执行的操作。
 * 
 * @param {string} label - 操作的显示标签
 * @param {Function} funct - 执行操作的函数
 * @param {boolean} enabled - 是否启用该操作
 * @param {string} iconCls - 图标CSS类名
 * @param {string} shortcut - 快捷键组合
 */
function Action(label, funct, enabled, iconCls, shortcut) {
  mxEventSource.call(this);
  this.label = label;
  this.funct = this.createFunction(funct);
  this.enabled = (enabled != null) ? enabled : true;
  this.iconCls = iconCls;
  this.shortcut = shortcut;
  this.visible = true;
};

// Action inherits from mxEventSource
mxUtils.extend(Action, mxEventSource);

/**
 * 创建操作执行函数。
 * 将传入的函数包装成操作的执行函数。
 * 
 * @param {Function} funct - 原始执行函数
 * @returns {Function} 包装后的执行函数
 */
Action.prototype.createFunction = function (funct) {
  return funct;
};

/**
 * 设置操作的启用状态并触发状态改变事件。
 * 当启用状态发生变化时会触发'stateChanged'事件。
 * 
 * @param {boolean} value - 新的启用状态
 */
Action.prototype.setEnabled = function (value) {
  if (this.enabled != value) {
    this.enabled = value;
    this.fireEvent(new mxEventObject('stateChanged'));
  }
};

/**
 * 返回操作的启用状态。
 * 检查该操作当前是否可用。
 * 
 * @returns {boolean} 如果操作启用则返回true，否则返回false
 */
Action.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * 设置操作是否为切换类型。
 * 将操作标记为切换类型，可在按下和取消两种状态之间切换。
 * 
 * @param {boolean} value - 是否为切换操作
 */
Action.prototype.setToggleAction = function (value) {
  this.toggleAction = value;
};

/**
 * 设置选中状态回调函数。
 * 用于判断切换类型操作是否处于选中状态。
 * 
 * @param {Function} funct - 返回选中状态的回调函数
 */
Action.prototype.setSelectedCallback = function (funct) {
  this.selectedCallback = funct;
};

/**
 * 返回操作的选中状态。
 * 调用选中状态回调函数来判断切换类型操作是否处于选中状态。
 * 
 * @returns {boolean} 如果操作处于选中状态则返回true，否则返回false
 */
Action.prototype.isSelected = function () {
  return this.selectedCallback();
};